var JavascriptMode = function (){
    var reString1 = /\\|'/g;
    var reString2 = /\\|"/g;
    var reRegExp = /\\|\//g;
    var reRegExpFlag = /[^gimsy]/g;
    var reDigit = /[^0-9]/g;
    var reId = /[^\w$_]/g;
    var reId1 = /^[\w$_]/;
    var reLineBreak = /\r?\n|\r/g;
    var reHexDigit = /[^0-9A-Fa-f]/g;
    var reOperator = /[^+\-*&%=<>!?|~^]/g;
    var reValid = /['"0-9\.\/\[\]{}\(\),;:+\-*&%=<>!?|~^\w$_]/g;
    var puncChars = make_map(chars('[]{}(),;:'));
    var operatorChars = make_map(chars('+-*&%=<>!?|~^'));
    var keywords = make_map([
        "break",
        "case",
        "catch",
        "const",
        "continue",
        "debugger",
        "default",
        "delete",
        "do",
        "else",
        "finally",
        "for",
        "function",
        "if",
        "in",
        "instanceof",
        "new",
        "return",
        "switch",
        "throw",
        "try",
        "typeof",
        "var",
        "void",
        "while",
        "with",

        "this",

        "abstract",
        "boolean",
        "byte",
        "char",
        "class",
        "double",
        "enum",
        "export",
        "extends",
        "final",
        "float",
        "goto",
        "implements",
        "import",
        "int",
        "interface",
        "long",
        "native",
        "package",
        "private",
        "protected",
        "public",
        "short",
        "static",
        "super",
        "synchronized",
        "throws",
        "transient",
        "volatile",

        "false",
        "null",
        "true"
    ]);
    var allowRegExps = make_map([
        'return',
        'break',
        'continue',
        'new',
        'delete',
        'throw',
        'in',
        'typeof',
        'instanceof'
    ], make_map(chars('[{}(,;:')));

    var Object$hop = Object.prototype.hasOwnProperty;
    function hop(obj, key){
        return Object$hop.call(obj, key);
    }
    function chars(str){
        return str.split('');
    }
    function make_map(array, ret){
        ret = ret || {};
        for (var j=array.length; j--;) {
            ret[array[j]] = true;
        }
        return ret;
    }

    function scan_string(stream, ch){
        var re = ch === '"' ? reString2 : reString1;
        while (stream.until(re) === '\\') {
            stream.next();
        }
    }

    function is_digit(ch){
        ch = ch.charCodeAt(0);
        return 48 <= ch && ch <= 57;
    }
    function scan_num(stream, ch){
        if (ch === '0') {
            ch = stream.ch;
            if (ch === 'x' || ch === 'X') {
                stream.next();
                stream.skipUntil(reHexDigit);
                return;
            }
        }
        var dot = ch === '.';
        var exp = false;
        while ((ch = stream.skipUntil(reDigit))) {
            if (ch === '.') {
                if (!dot) {
                    dot = true;
                    stream.next();
                } else {
                    return;
                }
            } else if (ch === 'e' || ch === 'E') {
                if (!exp) {
                    exp = true;
                    dot = false;
                    stream.next();
                    ch = stream.ch;
                    if (ch === '+' || ch === '-') {
                        stream.next();
                    }
                } else {
                    return;
                }
            } else {
                return;
            }
        }
    }
    var kwAllowed;
    var reAllowed;
    function scan(stream, state){
        var re;
        var ch = stream.ch;
        stream.next();
        if (ch === '"' || ch === "'") {
            scan_string(stream, ch);
            return 'string';
        } else if (is_digit(ch)) {
            scan_num(stream, ch);
            return 'number';
        } else if (ch === '.') {
            if (is_digit(stream.ch)) {
                scan_num(stream, ch);
                return 'number';
            } else {
                kwAllowed = false;
                return 'operator';
            }
        } else if (ch === '/') {
            ch = stream.ch;
            if (ch === '*') {
                stream.until('*/');
                return 'comment';
            } else if (ch === '/') {
                stream.until(reLineBreak);
                return 'comment';
            } else if (state.reAllowed) {
                while (stream.until(reRegExp) === '\\') {
                    stream.next();
                }
                stream.skipTo(reRegExpFlag);
                return 'regexp';
            } else {
                stream.skipTo(reOperator);
                return 'operator';
            }
        } else if (operatorChars[ch]) {
            stream.skipTo(reOperator);
            return 'operator';
        } else if (puncChars[ch]) {
            reAllowed = hop(allowRegExps, ch);
            return 'punc';
        } else if (reId1.test(ch)) {
            stream.skipUntil(reId);
            var word = stream.current();
            if (state.kwAllowed && hop(keywords, word)) {
                reAllowed = hop(allowRegExps, word);
                return 'keyword';
            }
            return 'name';
        } else {
            stream.skipUntil(reValid);
            return null;
        }
    }
    return {
        startState: function (){
            return {
                kwAllowed: true,
                reAllowed: true
            };
        },
        token: function (stream, state){
            kwAllowed = true;
            reAllowed = false;
            var type = scan(stream, state);
            if (type) {
                state.kwAllowed = kwAllowed;
                state.reAllowed = reAllowed || type === 'operator';
            }
            return type;
        }
    };
};
